﻿using Neoit.Utils.DataModel;
using Neoit.Utils.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Neoit.Utils.Tools
{
    /// <summary>
    /// Cron Expression
    /// </summary>
    public static class CronExpression
    {
        #region 解析Cron表达式为中文表述
        /* 1. 对于字符串表达式而言: 5个元素(* * * * *)对应[分，时，日，月，周]，6个元素对应[case1: 秒开始，case2: 年结束]、7个元素时对应[(秒)，分，时，日，月，周，(年)]
         * 2. 各元素取值规则： 任意值*、单独值1、区间2-5、列表2,5,6、间隔8-18/4(8开始，每隔4)
         *      秒 0-59 , - * /
         *      分 0-59 , - * /
         *      时 0-23 , - * /
         *      日 1-31 , - * ? / L W C
         *      月 1-12 或者 JAN-DEC , - * /
         *      周 0-6 或者 SUN-SAT , - * / ? L C #
         *      年（可选） 留空, 1970-2099 , - * /
         *  3. 日、周互斥，其中必有一个是"?"
         *  4. L：Last 最后；W：Weekday 工作日 (10W 指离10号最近的工作日, 若10号是周六则为9号，若10号为周日，则为11号)  C：Calendar 计划
         */
        /// <summary>
        /// 解析Cron表达式为中文表述
        /// </summary>
        /// <param name="cronExpression"></param>
        /// <param name="isParseSecond">是否解析秒</param>
        /// <param name="isValid"></param>
        /// <returns></returns>
        public static object ParseCronExpression(this string cronExpression, bool isParseSecond = true, bool isValid = true)
        {
            var errorMsg = "cron 表达式格式有误！";
            cronExpression = cronExpression.ToUpper();
            // (Seconds) Minutes Hours Day Month Week (Year)// 分为5/6/7个域
            var cronParts = cronExpression.Split(' ').Select(s => s.Trim()).Where(s => s.Has()).ToList();
            string Seconds, Minutes, Hours, Day, Month, Week, Year = "";
            
            #region 解析值
            if (cronParts.Count == 5)
            {
                cronParts.Insert(0, "");
                cronParts.Add("*");
            }
            else if (cronParts.Count == 6)
            {
                if (isParseSecond) cronParts.Add("*");
                else cronParts.Insert(0, "");
            }
            else if (cronParts.Count != 7)
            {
                throw new Exception(errorMsg);
            }

            Seconds = cronParts[0];
            Minutes = cronParts[1];
            Hours = cronParts[2];
            Day = cronParts[3];
            Month = cronParts[4];
            Week = cronParts[5];
            Year = cronParts[6];
            bool isWeek = Week != "?";
            #endregion

            var list = new List<string>();
            var listKey = new string[] { "秒", "分", "时", "日", "月", "周", "年" }; //几时
            //优化性表述：
            var listKeyPer = new string[] { "秒", "分钟", "小时", "天", "月", "周", "年" };//每x小时

            for (int i = 0; i < cronParts.Count; i++)
            {
                var part = cronParts[i];
                if (!part.Has()) continue;
                #region 校验范围合法性-基础性的校验（对已知的规则进行校验而非100%校验）
                if (isValid)
                {
                    switch (i)
                    {
                        case 0://秒
                            if (!isParseSecond) continue;
                            if (!isCronItem(part, "0-59")) throw new Exception(errorMsg); //秒、分: 0~59, 1-2, *
                            break;
                        case 1://分
                            if (!isCronItem(part, "0-59")) throw new Exception(errorMsg); //秒、分: 0~59, 1-2, *
                            break;
                        case 2://时
                            if (!isCronItem(part, "0-23")) throw new Exception(errorMsg);//时: 0-23, 1-2, *
                            break;
                        case 3://日
                            if (!isCronItem(part, "1-31")) throw new Exception(errorMsg);//日: 1~31, 1-2, *
                            break;
                        case 4://月
                            var months = ConstantData.Months.Select(s => s.EnAbbr.ToUpper()).ToList();
                            months.ForEach((i, s) => part = part.Replace(s, $"{i + 1}"));
                            if (!isCronItem(part, "1-12", false)) throw new Exception(errorMsg);//周
                            break;
                        case 5://周
                            part = part.Replace("7", "0");// “周”中7等价于0 都是周日
                            var weeks = ConstantData.Weeks.Select(s => s.EnAbbr.ToLower()).ToList();
                            weeks.ForEach((i, s) => part = part.Replace(s, $"{i}"));
                            if (!isCronItem(part, "0-6", false)) throw new Exception(errorMsg);//周
                            break;
                        case 6://年
                            if (!isCronItem(part, "0-90999")) throw new Exception(errorMsg);//年
                            break;
                        default:
                            throw new Exception(errorMsg);
                    }
                    if(Day.IsMatch("[?]") && Week.IsMatch("[?]")) throw new Exception(errorMsg);
                }
                #endregion
                var keyword = listKey[i];
                var keywordPer = listKeyPer[i];
                string item;
                if ("?" == part)
                {
                    item = "";
                }
                else if (part.Contains("L"))//L：该字符只在日和星期字段中使用，代表“Last”的意思
                {
                    if (i == 3)//日
                    {
                        if (part.Contains("-"))
                        {
                            item = $"倒数第{part.Split('-')[1]}{keyword}";
                        }
                        else
                        {
                            item = "最后一" + keyword;
                        }
                    }
                    else//周：6L；L；
                    {
                        if (part == "L")
                        {
                            item = "最后一个周日";
                        }
                        else
                        {
                            var temp = Convert.ToInt16(part.Match(@"\d")) - 1;
                            var partWeek = ConstantData.Weeks.FirstOrDefault(s => s.Index == temp)?.Cn;
                            item = "最后一个周" + partWeek;
                        }
                    }
                }
                else if (part.IsMatch(@"(\d+)W"))//W：该字符只在日字段中使用，代表离某日最近的工作日
                {
                    item = $"离{part.MatchGroupFirst(@"(\d+)W")}日最近的工作日";
                }
                else if (part.Contains("#"))//只出现“周”中如：6#3
                {
                    var rang = part.Split('#');
                    var temp = Convert.ToInt16(rang[0]) - 1;
                    var partWeek = ConstantData.Weeks.FirstOrDefault(s => s.Index == temp)?.Cn;
                    item = $"第{rang[1]}个周{partWeek}";
                }
                else if ("*" == part)
                {
                    item = "每" + keyword;
                }
                else if (part.Contains("-"))//范围
                {
                    if (i == 5 && part != "?")//周
                    {
                        var rang = part.Split('-');
                        if (rang[0].IsNaturalNumber() && rang[1].IsNaturalNumber())// 周-数值表达法
                        {
                            var partWeekStart = ConstantData.Weeks.FirstOrDefault(s => s.Number.ToString() == (rang[0] == "7" ? "0" : rang[0]))?.Cn;
                            var partWeekEnd = ConstantData.Weeks.FirstOrDefault(s => s.Number.ToString() == (rang[1] == "7" ? "0" : rang[1]))?.Cn;
                            item = $"，周{partWeekStart}至周{partWeekEnd}的每天";
                        }
                        else// 周-En缩写表达法
                        {
                            var partWeekStart = ConstantData.Weeks.FirstOrDefault(s => s.EnAbbr.ToLower() == rang[0].ToLower())?.Cn;
                            var partWeekEnd = ConstantData.Weeks.FirstOrDefault(s => s.EnAbbr.ToLower() == rang[1].ToLower())?.Cn;
                            item = $"，周{partWeekStart}至周{partWeekEnd}的每天";
                        }
                    }
                    else
                    {
                        item = $"，{part}{keyword}的每{keyword}";
                    }
                }
                else if (part.Contains(","))//枚举
                {
                    var combine = part.Split(",").Select(s => s + keyword).JoinString("、");
                    item = $"{combine}的";
                }
                else if (part.Contains("/"))//间隔
                {
                    var rang = part.Split('/');
                    var start = rang[0].Replace("*", "0");
                    var interval = rang[1] == "1" ? "" : rang[1];//每1日>每日
                    if (start == "0")
                    {
                        item = $"每{interval}{keyword}";
                    }
                    else
                    {
                        item = $"从{start}{keyword}始，间隔{rang[1]}{keyword}";
                    }
                }
                else if (i == 5 && part != "?")
                {
                    var partWeek = ConstantData.Weeks.FirstOrDefault(s => s.EnAbbr.ToLower() == part.ToLower())?.Cn;
                    item = $"每周{partWeek}";
                }
                else
                {
                    item = part + keyword;
                }
                list.Add(item);
            }
            //var isWeek = list[5] != "";
            var indexWeek = isParseSecond ? 5 : 4;
            var indexMonth = indexWeek;
            if (isWeek)//“周”有值时，应在月表述之后
            {
                var tmp = list[indexWeek];
                list[indexWeek] = list[indexWeek-1];
                list[indexWeek - 1] = tmp;
            }
            else
            {
                //list.RemoveAt(5);
            }
            if (isWeek && list[indexMonth] == "")//“周”有值 & 月为空时，移除月、年表述
            {
                list = list.Where(s => list.IndexOf(s) != indexMonth && list.IndexOf(s) != (indexMonth+1)).ToList();
            }
            list.Reverse();
            var res = list.JoinString("");
            #region 简化表述
            //简化时间
            if (isParseSecond)
            {
                var pa = @"(\d+)时(\d+)分(\d+)秒";
                if (res.IsMatch(pa))
                {
                    var g = res.MatchGroup(pa).ToList();
                    res = res.ReplaceRegex(pa, $"{g[1]}:{g[2].PadLeft(2, '0')}:{g[3].PadLeft(2, '0')}");
                }
            }
            else
            {
                var pa = @"(\d+)时(\d+)分";
                if (res.IsMatch(pa))
                {
                    var g = res.MatchGroup(pa).ToList();
                    res = res.ReplaceRegex(pa, $"{g[1]}:{g[2].PadLeft(2, '0')}");
                }
            }

            //00分:00秒时，省略秒
            res = res.ReplaceRegex(@"(\d+:\d+):00", "$1");
            //简化“每”
            var patternPer = "(每.)+每";
            if (res.IsMatch(patternPer))
            {
                var matchVal = res.Match(patternPer);
                res = res.ReplaceRegex(patternPer, "每");
            }
            //每分>每分钟    每时>每小时  每日>每天
            res = res.ReplaceRegex(@"(每\d*)分", "$1分钟").ReplaceRegex(@"(每\d*)时", "$1小时").ReplaceRegex(@"(每|最后.|倒数第.\d*)日", "$1天");
            //年>年的
            //res = res.ReplaceRegex(@"年", "年的");
            //每+
            res = res.ReplaceRegex(@"(.+)每", "$1里的每").ReplaceRegex("的里的", "里的");
            //0秒/0分钟 > ""
            res = res.ReplaceRegex(@"(\D)0秒", "$1").ReplaceRegex(@"(\D)0分钟?", "$1");

            res = res.TrimEnd('的').TrimStart('，');

            foreach (var item in ConstantData.Months)
            {
                if (res.ContainsRegexIgnoreCase(item.EnAbbr))
                {
                    res = res.ReplaceRegex(item.EnAbbr, item.Number.ToString(), RegexOptions.IgnoreCase);
                }
            }
            #endregion
            return res;

            bool existCronChar(string val)
            {
                return val.IsMatch(@"[*,/?L\-#]");
            }
            bool isCronItem(string val, string range, bool isGrowthScope = true)
            {
                var baseChar = "*,-/";
                //if (!val.IsMatch(@$"[{baseChar}\d]")) return false;

                var range1 = Convert.ToInt32(range.Split("-")[0]);
                var range2 = Convert.ToInt32(range.Split("-")[1]);

                if (val.IsMatch(@"^\d$")) //case1: 单个值
                {
                    if (!val.IsRangeInt(range1, range2)) return false;
                }
                else if (val == "*") //case2: *
                {
                    return true;
                }
                else
                {
                    if (val.IsMatch(",")) //case3: 1,2,5-8\2
                    {
                        if (val.Split(",").Where(s => !s.IsMatch("-|/")).Any(s => !s.IsRangeInt(range1, range2))) return false;
                    }
                    //case4: 1-5/6
                    if (val.IsMatch(@"\d+-\d+"))
                    {
                        var vals = val.MatchGroup(@"(\d+)-(\d+)").ToList();
                        if (!vals[1].IsRangeInt(range1, range2)) return false;
                        if (!vals[2].IsRangeInt(range1, range2)) return false;
                        if (isGrowthScope && (Convert.ToInt32(vals[1]) >= Convert.ToInt32(vals[2]))) return false;
                    }
                    //case5: 1-5/6
                    if (val.IsMatch(@"/"))
                    {
                        var valNumStr = val.MatchGroupFirst(@"/(\d+)");
                        if (!valNumStr.IsRangeInt(range1, range2)) return false;
                    }
                }

                return true;
            }

        }
        /// <summary>
        /// 解析Cron表达式为中文表述
        /// </summary>
        /// <param name="cronExpression"></param>
        /// <param name="isValid">是否进行基础校验</param>
        /// <returns></returns>
        public static object ParseCronBySecond(this string cronExpression, bool isValid = true)
        {
            return cronExpression.ParseCronExpression(true, isValid);
        }
        /// <summary>
        /// 解析Cron表达式为中文表述
        /// </summary>
        /// <param name="cronExpression"></param>
        /// <param name="isValid">是否进行基础校验</param>
        /// <returns></returns>
        public static object ParseCronByMinute(this string cronExpression, bool isValid = true)
        {
            return cronExpression.ParseCronExpression(false, isValid);
        }
        #endregion

    }
}
